<template>
  <component ref="formEl" :is="formRegister ? 'w-form-element' : 'div'" v-bind="
      formRegister && { validators, inputValue, disabled, readonly, isFocused }
    " v-model:valid="valid" @reset="
      $emit('update:modelValue', (inputValue = ''));
      $emit('input', '');
    " :class="classes"><template v-if="labelPosition === 'left'"><label
        class="w-textarea__label w-textarea__label--left w-form-el-shakable" v-if="$slots.default"
        :for="`w-textarea--${_.uid}`">
        <slot></slot>
      </label><label class="w-textarea__label w-textarea__label--left w-form-el-shakable" v-else-if="label"
        :for="`w-textarea--${_.uid}`" v-html="label"></label></template>
    <div class="w-textarea__textarea-wrap" :class="inputWrapClasses">
      <w-icon class="w-textarea__icon w-textarea__icon--inner-left" v-if="innerIconLeft" tag="label"
        :for="`w-textarea--${_.uid}`" @click="$emit('click:inner-icon-left', $event)">{{ innerIconLeft }}</w-icon>
      <textarea class="w-textarea__textarea" ref="textareaRef" v-model="inputValue" v-on="listeners" @input="onInput"
        @focus="onFocus" @blur="onBlur" :id="`w-textarea--${_.uid}`" :name="inputName"
        :placeholder="placeholder || null" :rows="rows || null" :cols="cols || null" :readonly="readonly || null"
        :aria-readonly="readonly ? 'true' : 'false'" :disabled="disabled || null" :required="required || null"
        :style="textareaStyles"></textarea><template v-if="labelPosition === 'inside' && showLabelInside"><label
          class="w-textarea__label w-textarea__label--inside w-form-el-shakable" v-if="$slots.default"
          :for="`w-textarea--${_.uid}`" :class="
            isFocused && {
              [valid === false ? 'error' : this.color]:
                this.color || valid === false
            }
          ">
          <slot></slot>
        </label><label class="w-textarea__label w-textarea__label--inside w-form-el-shakable" v-else-if="label"
          :for="`w-textarea--${_.uid}`" v-html="label" :class="
            isFocused && {
              [valid === false ? 'error' : color]: color || valid === false
            }
          "></label></template>
      <w-icon class="w-textarea__icon w-textarea__icon--inner-right" v-if="innerIconRight" tag="label"
        :for="`w-textarea--${_.uid}`" @click="$emit('click:inner-icon-right', $event)">{{ innerIconRight }}</w-icon>
    </div>
    <template v-if="labelPosition === 'right'"><label
        class="w-textarea__label w-textarea__label--right w-form-el-shakable" v-if="$slots.default"
        :for="`w-textarea--${_.uid}`">
        <slot></slot>
      </label><label class="w-textarea__label w-textarea__label--right w-form-el-shakable" v-else-if="label"
        :for="`w-textarea--${_.uid}`" v-html="label"></label></template>
  </component>
</template>

<script>
  /**
   * @todo Share the common parts between w-input, w-textarea & w-select.
   **/

  import { ref, computed, watch, getCurrentInstance, onMounted } from "vue";
  export default {
    name: "w-textarea",
    inject: { formRegister: { default: null } },

    props: {
      modelValue: { default: "" },
      label: { type: String },
      labelPosition: { type: String, default: "inside" },
      innerIconLeft: { type: String },
      innerIconRight: { type: String },
      // When label is inside, allows to move the label above on focus or when filled.
      staticLabel: { type: Boolean },
      placeholder: { type: String },
      color: { type: String, default: "primary" },
      bgColor: { type: String },
      dark: { type: Boolean },
      outline: { type: Boolean },
      shadow: { type: Boolean },
      noAutogrow: { type: Boolean },
      resizable: { type: Boolean }, // Toggle the HTML built-in bottom right corner resize handle.
      tile: { type: Boolean },
      rows: { type: [Number, String], default: 3 },
      cols: { type: [Number, String] },
      // Also name, disabled, readonly, required and validators in the mixin.
      name: { type: String }, // When sending data through form.
      disabled: { type: Boolean },
      readonly: { type: Boolean },
      required: { type: Boolean },
      validators: { type: Array }
    },

    emits: [
      "input",
      "update:modelValue",
      "focus",
      "blur",
      "click:inner-icon-left",
      "click:inner-icon-right"
    ],
    setup(props, ctx) {
      const instance = getCurrentInstance();
      const inputValue = ref(props.modelValue);
      const isFocused = ref(false);
      // When autogrow, calculate the height from the number of carriage return & font size.
      const height = ref(null);
      const lineHeight = ref(null);
      const paddingY = ref(null);
      const valid = ref(null);
      const textareaRef = ref(null);

      const inputName = computed(() => {
        return (
          props.name || `${instance.type.name}--${instance.type.uid || instance.uid}`
        );
      });

      const listeners = computed(() => {
        // Remove the events that are fired separately, so they don't fire twice.
        // eslint-disable-next-line no-unused-vars
        const { input, focus, blur, ...listeners } = instance.type.emits;
        return listeners;
      });
      const hasValue = computed(() => {
        return inputValue.value;
      });
      const hasLabel = computed(() => {
        return props.label || ctx.slots.default;
      });
      const showLabelInside = computed(() => {
        return !props.staticLabel || (!hasValue.value && !props.placeholder);
      });
      const classes = computed(() => {
        return {
          "w-textarea": true,
          "w-textarea--disabled": props.disabled,
          "w-textarea--readonly": props.readonly,
          [`w-textarea--${hasValue.value ? "filled" : "empty"}`]: true,
          "w-textarea--focused": isFocused.value,
          "w-textarea--dark": props.dark,
          "w-textarea--resizable": props.resizable,
          "w-textarea--floating-label":
            hasLabel.value &&
            props.labelPosition === "inside" &&
            !props.staticLabel &&
            !(props.readonly && !hasValue.value),
          "w-textarea--no-padding":
            !props.outline && !props.bgColor && !props.shadow,
          "w-textarea--has-placeholder": props.placeholder,
          "w-textarea--inner-icon-left": props.innerIconLeft,
          "w-textarea--inner-icon-right": props.innerIconRight
        };
      });
      const inputWrapClasses = computed(() => {
        return {
          [valid.value === false ? "error" : props.color]:
            props.color || valid.value === false,
          [`${props.bgColor}--bg`]: props.bgColor,
          "w-textarea__textarea-wrap--tile": props.tile,
          // Box adds a padding on input. If there is a bgColor or shadow, a padding is needed.
          "w-textarea__textarea-wrap--box":
            props.outline || props.bgColor || props.shadow,
          "w-textarea__textarea-wrap--underline": !props.outline,
          "w-textarea__textarea-wrap--shadow": props.shadow,
          "w-textarea__textarea-wrap--no-padding":
            !props.outline && !props.bgColor && !props.shadow
        };
      });

      const textareaStyles = computed(() => {
        if (props.noAutogrow || props.resizable) return {};

        return {
          height: height.value ? `${height.value}px` : null
        };
      });
      function onInput() {
        if (!props.noAutogrow && !props.resizable) {
          computeHeight();
        }
        ctx.emit("update:modelValue", inputValue.value);
        ctx.emit("input", inputValue.value);
      }
      function onFocus(e) {
        isFocused.value = true;
        ctx.emit("focus", e);
      }
      function onBlur(e) {
        isFocused.value = false;
        ctx.emit("blur", e);
      }
      function computeHeight() {
        const lines = (inputValue.value.match(/\n/g) || []).length + 1;
        height.value =
          Math.max(lines, props.rows) * lineHeight.value + paddingY.value;
      }
      function getLineHeight() {
        const computedStyles = window.getComputedStyle(textareaRef.value, null);
        lineHeight.value = parseFloat(
          computedStyles.getPropertyValue("line-height")
        );
        paddingY.value = parseFloat(
          computedStyles.getPropertyValue("padding-top")
        );
        paddingY.value += parseFloat(
          computedStyles.getPropertyValue("padding-bottom")
        );
      }

      onMounted(() => {
        if (!props.noAutogrow && !props.resizable) {
          getLineHeight();
        }
      });

      watch(
        () => props.modelValue,
        value => {
          inputValue.value = value;
          computeHeight();
        }
      );
      watch(
        () => props.resizable,
        value => {
          if (value) {
            height.value = null;
          } else if (!props.noAutogrow) {
            getLineHeight();
          }
        }
      );
      watch(
        () => props.noAutogrow,
        value => {
          if (value) {
            getLineHeight();
          } else {
            height.value = null;
          }
        }
      );

      return {
        inputValue,
        isFocused,
        height,
        lineHeight,
        paddingY,
        valid,
        // refs
        textareaRef,
        // computed
        inputName,
        listeners,
        hasValue,
        hasLabel,
        showLabelInside,
        classes,
        inputWrapClasses,
        textareaStyles,
        // methods
        onInput,
        onFocus,
        onBlur,
        computeHeight,
        getLineHeight
      };
    }
  };
</script>